#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2020, Red Hat
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function

__metaclass__ = type


DOCUMENTATION = r'''

module: k8s_exec

short_description: Execute command in Pod

version_added: "0.10.0"

author: "Tristan de Cacqueray (@tristanC)"

description:
  - Use the Kubernetes Python client to execute command on K8s pods.

extends_documentation_fragment:
  - kubernetes.core.k8s_auth_options

requirements:
  - "python >= 3.6"
  - "kubernetes >= 12.0.0"
  - "PyYAML >= 3.11"

notes:
  - Return code C(rc) for the command executed is added in output in version 2.2.0, and deprecates return code C(return_code).
  - Return code C(return_code) for the command executed is added in output in version 1.0.0.
  - The authenticated user must have at least read access to the pods resource and write access to the pods/exec resource.

options:
  proxy:
    description:
    - The URL of an HTTP proxy to use for the connection.
    - Can also be specified via I(K8S_AUTH_PROXY) environment variable.
    - Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).
    type: str
  namespace:
    description:
    - The pod namespace name
    type: str
    required: yes
  pod:
    description:
    - The pod name
    type: str
    required: yes
  container:
    description:
    - The name of the container in the pod to connect to.
    - Defaults to only container if there is only one container in the pod.
    type: str
    required: no
  command:
    description:
    - The command to execute
    type: str
    required: yes
'''

EXAMPLES = r'''
- name: Execute a command
  kubernetes.core.k8s_exec:
    namespace: myproject
    pod: zuul-scheduler
    command: zuul-scheduler full-reconfigure

- name: Check RC status of command executed
  kubernetes.core.k8s_exec:
    namespace: myproject
    pod: busybox-test
    command: cmd_with_non_zero_exit_code
  register: command_status
  ignore_errors: True

- name: Check last command status
  debug:
    msg: "cmd failed"
  when: command_status.rc != 0
'''

RETURN = r'''
result:
  description:
  - The command object
  returned: success
  type: complex
  contains:
     stdout:
       description: The command stdout
       type: str
     stdout_lines:
       description: The command stdout
       type: str
     stderr:
       description: The command stderr
       type: str
     stderr_lines:
       description: The command stderr
       type: str
     rc:
       description: The command status code
       type: int
       version_added: 2.2.0
     return_code:
       description: The command status code. This attribute is deprecated and will be removed in a future release. Please use rc instead.
       type: int
'''

import copy
import shlex

try:
    import yaml
except ImportError:
    # ImportError are managed by the common module already.
    pass

from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
    AUTH_ARG_SPEC
)

try:
    from kubernetes.client.apis import core_v1_api
    from kubernetes.stream import stream
except ImportError:
    # ImportError are managed by the common module already.
    pass


def argspec():
    spec = copy.deepcopy(AUTH_ARG_SPEC)
    spec['namespace'] = dict(type='str', required=True)
    spec['pod'] = dict(type='str', required=True)
    spec['container'] = dict(type='str')
    spec['command'] = dict(type='str', required=True)
    return spec


def execute_module(module, k8s_ansible_mixin):

    # Load kubernetes.client.Configuration
    api = core_v1_api.CoreV1Api(k8s_ansible_mixin.client.client)

    # hack because passing the container as None breaks things
    optional_kwargs = {}
    if module.params.get('container'):
        optional_kwargs['container'] = module.params['container']
    try:
        resp = stream(
            api.connect_get_namespaced_pod_exec,
            module.params["pod"],
            module.params["namespace"],
            command=shlex.split(module.params["command"]),
            stdout=True,
            stderr=True,
            stdin=False,
            tty=False,
            _preload_content=False, **optional_kwargs)
    except Exception as e:
        module.fail_json(msg="Failed to execute on pod %s"
                             " due to : %s" % (module.params.get('pod'), to_native(e)))
    stdout, stderr, rc = [], [], 0
    while resp.is_open():
        resp.update(timeout=1)
        if resp.peek_stdout():
            stdout.append(resp.read_stdout())
        if resp.peek_stderr():
            stderr.append(resp.read_stderr())
    err = resp.read_channel(3)
    err = yaml.safe_load(err)
    if err['status'] == 'Success':
        rc = 0
    else:
        rc = int(err['details']['causes'][0]['message'])

    module.deprecate("The 'return_code' return key is deprecated. Please use 'rc' instead.", version="4.0.0", collection_name="kubernetes.core")
    module.exit_json(
        # Some command might change environment, but ultimately failing at end
        changed=True,
        stdout="".join(stdout),
        stderr="".join(stderr),
        rc=rc,
        return_code=rc
    )


def main():
    module = AnsibleModule(
        argument_spec=argspec(),
        supports_check_mode=True,
    )
    from ansible_collections.kubernetes.core.plugins.module_utils.common import (
        K8sAnsibleMixin, get_api_client)

    k8s_ansible_mixin = K8sAnsibleMixin(module)
    k8s_ansible_mixin.client = get_api_client(module=module)
    execute_module(module, k8s_ansible_mixin)


if __name__ == '__main__':
    main()
